#pragma once
#include "common.h"
#include "fiber.h"
#include "scheduler.h"

template<typename mutex_t>
class condition_variable
{
public:
	enum class cv_status
	{
		no_timeout, 
		timeout
	};
	condition_variable()
	{

	}
	void wait(unique_locker &lock)
	{
		auto _fiber = current_fiber;
		_fiber->set_status(fiber::status::e_blocking);
		blocking_fiber_queue_.push_back(current_fiber);
		lock.unlock();
		_fiber->yield();
		lock.lock();
	}
	cv_status wait_for(unique_locker &lock, uint32_t milliseconds)
	{
		auto timeout = milliseconds;
		auto begin_timepoint = high_resolution_clock::now();
		auto _fiber = current_fiber;

		_fiber->set_status(fiber::status::e_wait);
		blocking_fiber_queue_.push_back(_fiber);
		lock.unlock();
to_sleep:
		_fiber->sleep(milliseconds);
		if (blocking_fiber_queue_.exist(_fiber))
		{
			blocking_fiber_queue_.erase(_fiber);
			return cv_status::timeout;
		}
		if (lock.try_lock() == false)
		{
			auto elapsed_time = static_cast<uint32_t>(
				std::chrono::duration_cast<std::chrono::milliseconds>
				(high_resolution_clock::now() - begin_timepoint).count());

			milliseconds = milliseconds > elapsed_time ?
				milliseconds - elapsed_time : 0;
			goto to_sleep;
		}
		return cv_status::no_timeout;
	}
	void notify_one()
	{
		if (blocking_fiber_queue_.size() == 0)
			return;
		auto _fiber = blocking_fiber_queue_.pop_front();
		if (_fiber->get_status() == fiber::status::e_wait)
		{
			_fiber->get_scheduler()->erase_fiber(_fiber);
		}
		_fiber->set_status(fiber::status::e_running);
		_fiber->get_scheduler()->append_fiber(_fiber);
	}
	void notify_all()
	{
		if (blocking_fiber_queue_.size() == 0)
			return;
		do 
		{
			auto _fiber = blocking_fiber_queue_.front();
			blocking_fiber_queue_.pop_front();

			if (_fiber->get_status() == fiber::status::e_wait)
			{
				_fiber->get_scheduler()->erase_fiber(_fiber);
			}
			_fiber->set_status(fiber::status::e_running);
			_fiber->get_scheduler()->append_fiber(_fiber);

		} while (blocking_fiber_queue_.size());
	}
private:
	spinlock_fiber_queue<spinlock_fiber_queue_name::condition_variable_queue> 
		blocking_fiber_queue_;
};